The Developer Fastlane

« 365 days to become a developer » challenge

PHP OOP: Use an API with cURL

November 27, 2020

Exercise (object oriented)

Instructions & result

Goal: Rewrite the lesson's code in an object-oriented manner and go further.
Time to exercise: 25:00
  1. Instructions

    List of classes and methods:
    • $meteo = new OpenWeater('b6907d289e10d714a6e88b30761fae22');
    • $meteo->getforecast('Montpellier,fr');
    Will return an array as follows:
    [
        [
            'temp' => 5.03,
            'description' => '...',
            'date' => DateTime()
        ]
    ]
    We want to display a line in footer like this (between <li> tags):
    • Jan 3, 2020: Cloudy (13°C)
Result
Click on one of the images above to access the exercise's website

Code

OpenWeather.php (class file)
Code
<?php

class OpenWeather {

    const ICON_SIZE = '@2x'; // '', '@2x' or '@4x'
    const UNITS = 'metric';

    private $api_key;

    public function __construct(string $var)
    {
        $this->api_key = $var;
    }
    public function getToday(array $coordinates): array
    {
        $array = [];
        $data = $this->callAPI($coordinates);
        if ($data !== null) {
            $array = [
                'temp'  => $data["current"]["temp"] . '°C',
                'description'  => $data["current"]["weather"][0]["description"],
                'icon'  => 'http://openweathermap.org/img/wn/' . $data["current"]["weather"][0]["icon"] . self::ICON_SIZE . '.png',
                'time'  => $this->getTimeFormated($data["current"]["dt"], $data['timezone']),
            ];
        }
        return $array;
    }
    public function getLastHours(array $coordinates): array
    {
        $array = [];
        $data = $this->callAPI($coordinates);
        if ($data !== null) {
            foreach ($data['hourly'] as $hour) {
                $array[] = [
                    'temp' => $hour["temp"] . '°C',
                    'description' => ucfirst($hour["weather"][0]["description"]),
                    'icon' => 'http://openweathermap.org/img/wn/' . $hour["weather"][0]["icon"] . self::ICON_SIZE . '.png',
                    'time' => $this->getTimeFormated($hour["dt"], $data['timezone']),
                ];
            }
        }
        return $array;
    }
    public function getTimeFormated($timestamp, $timezone): array
    {
        $date = new DateTime("@" . $timestamp);
        $date->setTimeZone(new DateTimeZone($timezone));
        return  [
            'all' => $date->format('M d H:i'),
            'date' => $date->format('M d'),
            'hour' => $date->format('H:i'),
        ];
    }
    private function callAPI(array $coordinates): ?array
    {
        $time = time();
        $latitude = $coordinates[0];
        $longitude = $coordinates[1];
        $unit = self::UNITS;
        $curl = curl_init("https://api.openweathermap.org/data/2.5/onecall/timemachine?lat={$latitude}&lon={$longitude}&dt=$time&units={$unit}&appid={$this->api_key}");
        curl_setopt_array($curl, [
            CURLOPT_CAINFO          => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'certificates' . DIRECTORY_SEPARATOR . 'openweather.cer',
            CURLOPT_RETURNTRANSFER  => true,
            CURLOPT_TIMEOUT_MS      => 1000
        ]);
        $data = curl_exec($curl);
        if ($data === false || curl_getinfo($curl, CURLINFO_HTTP_CODE) !== 200) {
            return null;
        }
        curl_close($curl);
        return json_decode($data, true);
    } 

}
weather.php (display weather)
Code
<?php 

$cities = [
    'paris'         => [48.85, 2.35],
    'kerikeri'      => [-35.22, 173.94],
    'new-york'      => [40.71, -74.00],
    'hong-kong'     => [22.39, 114.10],
];

$title = 'Weather forecast';
require_once 'includes/header.php';
require_once 'class/OpenWeather.php';
$weather = new OpenWeather('4f34eb4cec4aa5200aa8b415963e297c');
?>

<p class="lead mb-5">The list below has been made through the OpenWeather API. I links the API to this website thanks to the curl extension for PHP and following <a href="/lessons/20201127_php_Use%20an%20API%20with%20cURL/">this lesson</a>.</p>
        
<div class="row">
    <?php 
    foreach ($cities as $name => $coordinates):
        $current = $weather->getToday($coordinates); 
        if (!empty($current)): ?>
            <div class="col-md-3 mb-5">
                <div class="card">
                    <div class="card-body text-center">
                        <h4><?= implode('-', array_map('ucfirst', explode('-', $name))) ?></h4>
                        <div class="small text-muted"><?= $current['time']['all'] ?></div>
                        <img src="<?= $current['icon']; ?>"/>
                        <div><?= $current['description'] ?></div>
                        <div><b><?= $current['temp'] ?></b></div>
                    </div>
                </div>
                <div class="mt-3">
                    <ul class="list-unstyled">
                        <?php $lastHours = $weather->getLastHours($coordinates); 
                        for ($i = count($lastHours) - 1; $i >= count($lastHours) - 10; $i--): ?>
                            <?php if (!empty($lastHours[$i])): ?>
                                <li class="small text-muted"><?= $lastHours[$i]['time']['hour'] . ' - <b>' . $lastHours[$i]['temp'] . '</b>  - ' . $lastHours[$i]['description'] ?></li>
                            <?php endif; ?>
                            <?php
                        endfor; ?>
                    </ul>
                </div>
            </div>
        <?php endif; ?>
    <?php endforeach; ?> 
</div>

<?php require 'includes/footer.php'; ?>

Lesson (procedural)

Go to exercise

Here is the final code to call an API (after completing all steps listed below, from 1 to 5):
Code
<?php
$curl = curl_init('https://samples.openweathermap.org/data/2.5/weather?q=Paris,fr&units=metric&APPID=b6907d289e10d714a6e88b30761fae22');
curl_setopt_array($curl, [
    CURLOPT_CAINFO          => __DIR__ . DIRECTORY_SEPARATOR . 'cert.cer',
    CURLOPT_RETURNTRANSFER  => true,
    CURLOPT_TIMEOUT_MS      => 1000
]);
$data = curl_exec($curl);
if ($data === false) {
    var_dump(curl_error($curl));
} else {
    if(curl_getinfo($curl, CURLINFO_HTTP_CODE) === 200) {
        $data = json_decode($data, true);
        echo 'Temperature: ' . $data['main']['temp'] . ' °C';
    }
}
curl_close($curl);

1. Activation of the curl extension on the local server

Here is a ready-to-go php.ini file to download (right click + "Save link as")

Steps:
  • Run a phpinfo(); command to get the info about your system. Look for a whole section called curl. If no section exists, then you have to install the extension via the php.ini file
  • Rename the php.ini-development (or ) file in the PHP installation folder (usually the folder named "PHP" located at the root of the C: hard drive on Windows).
  • Unmute the following ligns (just remove the semicolon at the begenning of each line):
    • extension_dir = "ext"
    • extension=curl
  • Stop and run again the PHP server

2. Execute the curl request with PHP

There are 4 essential functions (look at code on top of the page for details / application example)
  • curl_init(): Initialize a cURL session (PHP Doc)
  • curl_exec(): Perform a cURL session (PHP Doc)
  • curl_error(): Return a string containing the last error for the current session (PHP Doc)
  • curl_close(): Close a cURL session (PHP Doc)

3. Manage SSL errors

2 solutions, both using curl_setopt() function (or more usually curl_setopt_array() one if passing several parameters):
  • For testing/development purpose only (poses security concerns): curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
  • Recommanded one (Download an SSL certificate) - Steps in Chrome browser:
    • Click on the "padlock" icon to the left of the address bar.
    • Click on "Certificate (valid)".
    • Go to the tab "Certification path".
    • Click on the root certificate (1st line of the tree) then click on the "View Certificate" button.
    • In the new window, click on "Details" and then click on the "Copy to file..." button.
    • In the Certificate Export Wizard, select the option "X.509 encoded in base 64 (*.cer)" (do not take the option "DER encoded" because it will not be understood by PHP)
    • Choose the path to save the file (example: create a folder "certificates" in the "config" folder). Name the file "cert.cer".
    • then order: curl_setopt($curl, CURLOPT_CAINFO, __DIR__ . DIRECTORY_SEPARATOR . 'cert.cer');

4. Decoding the content of the cURL request

Steps:
  • Store the content of the query in a $data variable with: curl_setopt_array( CURLOPT_RETURNTRANSFER => true);
  • decode the JSON content with: json_decode($data, true);
  • Retrieve the useful key with: var_dump($data['key']); for example or any other PHP display function.
  • Tip: to more easily find the keys in the JSON file, use Firefox to open the JSON file or use an extension in Chrome (will display a more readable hierarchical list of JSON content, with colors, etc.).
  • Finally, define a timeout with: curl_setopt_array( CURLOPT_TIMEOUT_MS => 1000);

5. Get API key and do some customizations to API data

Example with the OpenWeather API:
  • Go to "How to start". Signup. Get API Key for the service needed.
  • Put the API key in the url with: &APPID=paste_api_key_here
  • Add to URL the needed parameters, based on the documentation

© 2020 - Edouard Proust | The Developer Fastlane